/*
Copyright (C) 2003-2004 Douglas Thain and the University of Wisconsin
Copyright (C) 2005- The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file COPYING for details.
*/

#include "datagram.h"
#include "stringtools.h"

#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <sys/un.h>

#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <netdb.h>
#include <errno.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct datagram {
       int fd;
};

struct datagram * datagram_create( int port )
{
	struct datagram *d=0;
	struct sockaddr_in address;
	int success;
	int on =1;

	d = malloc(sizeof(*d));
	if(!d) goto failure;

	d->fd = socket( PF_INET, SOCK_DGRAM, 0 );
	if(d->fd<0) goto failure;

	setsockopt( d->fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on) );

	if(port!=DATAGRAM_PORT_ANY) {
		address.sin_family = AF_INET;
		address.sin_port = htons(port);
		address.sin_addr.s_addr = htonl(INADDR_ANY);

		success = bind( d->fd, (struct sockaddr *) &address, sizeof(address) );
		if(success<0) goto failure;
	}

	return d;

	failure:
	datagram_delete(d);
	return 0;
}

void datagram_delete( struct datagram *d )
{
	if(d) {
		if(d->fd>=0) close(d->fd);
		free(d);
	}
}

static void addr_to_string( struct in_addr *addr, char *str )
{
	unsigned char *bytes;
	bytes = (unsigned char*) addr;

	sprintf(str,"%u.%u.%u.%u",
		(unsigned)bytes[0],
		(unsigned)bytes[1],
		(unsigned)bytes[2],
		(unsigned)bytes[3]);
}

static int errno_is_temporary( int e )
{
	if( e==EINTR || e==EWOULDBLOCK || e==EAGAIN || e==EINPROGRESS || e==EALREADY || e==EISCONN ) {
		return 1;
	} else {
		return 0;
	}
}

#if defined(__GLIBC__) || defined(CCTOOLS_OPSYS_DARWIN)
	#define SOCKLEN_T socklen_t
#else
	#define SOCKLEN_T int
#endif

int datagram_recv( struct datagram *d, char *data, int length, char *addr, int *port, int timeout )
{
	int result;
	struct sockaddr_in iaddr;
	SOCKLEN_T iaddr_length;
	fd_set fds;
	struct timeval tm;

	while(1) {

		tm.tv_sec = timeout/1000000;
		tm.tv_usec = timeout%1000000;

		FD_ZERO(&fds);
		FD_SET(d->fd,&fds);

		result = select(d->fd+1,&fds,0,0,&tm);
		if(result>0) {
			if(FD_ISSET(d->fd,&fds)) break;
		} else if( result<0 && errno_is_temporary(errno) ) {
			continue;
		} else {
			return -1;
		}
	}

	iaddr_length = sizeof(iaddr);

	result = recvfrom( d->fd, data, length, 0, (struct sockaddr *) &iaddr, &iaddr_length );
	if(result<0) return result;

	addr_to_string( &iaddr.sin_addr, addr );
	*port = ntohs( iaddr.sin_port );

	return result;
}

int datagram_send( struct datagram *d, const char *data, int length, const char *addr, int port )
{
	int result;
	struct sockaddr_in iaddr;
	SOCKLEN_T iaddr_length;

	iaddr_length = sizeof(iaddr);

	iaddr.sin_family = AF_INET;
	iaddr.sin_port = htons(port);
	if(!string_to_ip_address( addr, (unsigned char *)&iaddr.sin_addr )) return -1;
	
	result = sendto( d->fd, data, length, 0, (const struct sockaddr *) &iaddr, iaddr_length );
	if(result<0) return result;

	return result;
}

int datagram_fd ( struct datagram *d )
{
	return d->fd;
}


